#include  "wx/wxprec.h"
#include "wx/dynlib.h"
#include "wx/ffile.h"

#ifdef HAVE_DLOPEN
#include <dlfcn.h>
#endif

#ifdef __DARWIN__
#include <AvailabilityMacros.h>
#endif

#ifndef RTLD_LAZY
#define RTLD_LAZY 0
#endif

#ifndef RTLD_NOW
#define RTLD_NOW 0
#endif

#ifndef RTLD_GLOBAL
#define RTLD_GLOBAL 0
#endif


#if defined(HAVE_DLOPEN) || defined(__DARWIN__)
#define USE_POSIX_DL_FUNCS
#elif !defined(HAVE_SHL_LOAD)
#error "Don't know how to load dynamic libraries on this platform!"
#endif

const wxChar *wxDynamicLibrary::ms_dllext = _T( ".so" );

#define wx_dlopen(__path,__mode) dlopen(__path,__mode)
#define wx_dlclose(__handle) dlclose(__handle)
#define wx_dlerror() dlerror()
#define wx_dlsym(__handle,__symbol) dlsym(__handle,__symbol)

wxDllType wxDynamicLibrary::GetProgramHandle() {
  #ifdef USE_POSIX_DL_FUNCS
  return wx_dlopen( 0, RTLD_LAZY );
  #else
  return PROG_HANDLE;
  #endif
}

wxDllType wxDynamicLibrary::RawLoad( const wxString& libname, int flags ) {
  wxASSERT_MSG( !( flags & wxDL_NOW ) || !( flags & wxDL_LAZY ),
                _T( "wxDL_LAZY and wxDL_NOW are mutually exclusive." ) );
  #ifdef USE_POSIX_DL_FUNCS
  int rtldFlags = flags & wxDL_LAZY ? RTLD_LAZY : RTLD_NOW;
  if( flags & wxDL_GLOBAL ) {
    rtldFlags |= RTLD_GLOBAL;
  }
  return wx_dlopen( libname.fn_str(), rtldFlags );
  #else // !USE_POSIX_DL_FUNCS
  int shlFlags = 0;
  if( flags & wxDL_LAZY ) {
    shlFlags |= BIND_DEFERRED;
  } else if( flags & wxDL_NOW ) {
    shlFlags |= BIND_IMMEDIATE;
  }
  return shl_load( libname.fn_str(), shlFlags, 0 );
  #endif // USE_POSIX_DL_FUNCS/!USE_POSIX_DL_FUNCS
}

void wxDynamicLibrary::Unload( wxDllType handle ) {
  #ifdef wxHAVE_DYNLIB_ERROR
  int rc =
  #endif
    #ifdef USE_POSIX_DL_FUNCS
    wx_dlclose( handle );
    #else // !USE_POSIX_DL_FUNCS
    shl_unload( handle );
    #endif // USE_POSIX_DL_FUNCS/!USE_POSIX_DL_FUNCS
  #if defined(USE_POSIX_DL_FUNCS) && defined(wxHAVE_DYNLIB_ERROR)
  if( rc != 0 ) {
    Error();
  }
  #endif
}

void *wxDynamicLibrary::RawGetSymbol( wxDllType handle, const wxString& name ) {
  void *symbol;
  #ifdef USE_POSIX_DL_FUNCS
  symbol = wx_dlsym( handle, name.fn_str() );
  #else // !USE_POSIX_DL_FUNCS
  if( shl_findsym( &handle, name.fn_str(), TYPE_UNDEFINED, &symbol ) != 0 ) {
    symbol = 0;
  }
  #endif
  return symbol;
}

#ifdef wxHAVE_DYNLIB_ERROR

void wxDynamicLibrary::Error() {
  wxWCharBuffer buffer = wxConvLocal.cMB2WC( wx_dlerror() );
  const wxChar *err = buffer;
  wxLogError( wxT( "%s" ), err ? err : _( "Unknown dynamic library error" ) );
}

#endif

class wxDynamicLibraryDetailsCreator {
  public:
    static wxDynamicLibraryDetails * New( void *start, void *end, const wxString& path ) {
      wxDynamicLibraryDetails *details = new wxDynamicLibraryDetails;
      details->m_path = path;
      details->m_name = path.AfterLast( _T( '/' ) );
      details->m_address = start;
      details->m_length = ( char * )end - ( char * )start;
      const size_t posExt = path.rfind( _T( ".so" ) );
      if( posExt != wxString::npos ) {
        if( path.c_str()[posExt + 3] == _T( '.' ) ) {
          details->m_version.assign( path, posExt + 4, wxString::npos );
        } else {
          size_t posDash = path.find_last_of( _T( '-' ), posExt );
          if( posDash != wxString::npos ) {
            posDash++;
            details->m_version.assign( path, posDash, posExt - posDash );
          }
        }
      }
      return details;
    }
};

wxDynamicLibraryDetailsArray wxDynamicLibrary::ListLoaded() {
  wxDynamicLibraryDetailsArray dlls;
  #ifdef __LINUX__
  wxFFile file( _T( "/proc/self/maps" ) );
  if( file.IsOpened() ) {
    wxString pathCur;
    void *startCur = NULL,
          *endCur = NULL;
    char path[1024];
    char buf[1024];
    while( fgets( buf, WXSIZEOF( buf ), file.fp() ) ) {
      void *start,
           *end;
      switch( sscanf( buf, "%p-%p %*4s %*p %*02x:%*02x %*d %1024s\n",
                      &start, &end, path ) ) {
        case 2:
          path[0] = '\0';
          break;
        case 3:
          break;
        default:
          buf[strlen( buf ) - 1] = '\0';
          wxLogDebug( _T( "Failed to parse line \"%s\" in /proc/self/maps." ),
                      buf );
          continue;
      }
      wxASSERT_MSG( start >= endCur,
                    _T( "overlapping regions in /proc/self/maps?" ) );
      wxString pathNew = wxString::FromAscii( path );
      if( pathCur.empty() ) {
        pathCur = pathNew;
        startCur = start;
        endCur = end;
      } else if( pathCur == pathNew && endCur == end ) {
        endCur = end;
      } else {
        dlls.Add( wxDynamicLibraryDetailsCreator::New( startCur,
                  endCur,
                  pathCur ) );
        pathCur.clear();
      }
    }
  }
  #endif
  return dlls;
}

